package com.coldwindblows.servlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.coldwindblows.annotation.Autowired;
import com.coldwindblows.annotation.Controller;
import com.coldwindblows.annotation.RequestMapping;
import com.coldwindblows.annotation.Service;
import com.coldwindblows.exception.ClassInstaceException;

public class DispatcherServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	private static List<String> classNames = new ArrayList<>();

	private static Map<String, Object> beanMap = new HashMap<>();

	private static Properties contextConfig = new Properties();
	
	private static Map<String, HandlerMapping> handlerMapping = new HashMap<>();

	public DispatcherServlet() {
		super();
		System.out.println("实例化DispatcherServlet");
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		// 初始化容器
		System.out.println("初始化spring容器");
		// 加载配置文件
		doLoadConfig(config.getInitParameter("contextConfigLocation"));
		// 扫描包
		scanPackage(contextConfig.getProperty("base-scan"));
		// 初始化实例
		try {
			getInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 自动注入
		doAutowired();
		// handlermapping映射
		doHandlerMapping();

		super.init(config);
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doDispatcher(request, response);
	}

	@Override
	public void destroy() {
		System.out.println("释放资源,销毁容器");
		super.destroy();
	}
	
	private void doDispatcher(HttpServletRequest request, HttpServletResponse response) throws IOException {
		String uri = request.getRequestURI();
		String contextPath = request.getContextPath();
		uri = uri.replace(contextPath, "").replaceAll("/+", "/");
		if(!handlerMapping.containsKey(uri)) {
			response.getWriter().write("404 Not Found!");
			return;
		}
		HandlerMapping hm = handlerMapping.get(uri);
		response.setCharacterEncoding("GBK");
		try {
			Object object = hm.getMethod().invoke(hm.getObject(), request, response);
			response.getWriter().write(object.toString());
		} catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
			e.printStackTrace();
			response.getWriter().write("500 Service Error!");
		}
	}

	private void doLoadConfig(String configName) {
		configName = configName.startsWith("classpath:") ? configName.substring(10) : configName;
		InputStream in = this.getClass().getClassLoader().getResourceAsStream(configName);
		try {
			contextConfig.load(in);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (null != in) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void scanPackage(String basePackage) {
		String[] packages = basePackage.split(",");
		URL url = null;
		File classDir = null;
		for (String pg : packages) {
			url = this.getClass().getClassLoader().getResource("/" + pg.replaceAll("\\.", "/"));
			classDir = new File(url.getFile());
			for (File file : classDir.listFiles()) {
				if (file.isDirectory()) {
					scanPackage(pg + "." + file.getName());
				} else {
					classNames.add(pg + "." + file.getName().replace(".class", ""));
				}
			}
		}
	}

	private void getInstance() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		// 根据反射实例化对象
		String beanName = null;
		Object object = null;
		for (String className : classNames) {
			// 只实例化有注解的类
			Class<?> clazz = Class.forName(className);
			// 有controller注解
			if (clazz.getAnnotation(Controller.class) != null) {
				beanName = getBeanName(clazz.getSimpleName());
			} else if (clazz.getAnnotation(Service.class) != null) {
				Service service = clazz.getAnnotation(Service.class);
				beanName = service.value().isEmpty() ? getBeanName(clazz.getSimpleName()) : service.value();
				// 返回所有直接实现类,没有实现类，数组为0
				Class<?>[] interfaces = clazz.getInterfaces();
				if(interfaces.length == 1) {
					beanName = getBeanName(interfaces[0].getSimpleName());
				} else if(interfaces.length > 1) {
					throw new ClassInstaceException(clazz.getSimpleName() + "new instance has ambiguous");
				}
			} else {
				continue;
			}
			object = clazz.newInstance();
			// 加入map
			beanMap.put(beanName, object);
		}
	}

	private void doAutowired() {
		if(beanMap.isEmpty()) {return;}
		for(Entry<String, Object> entry : beanMap.entrySet()) {
			for(Field fd : entry.getValue().getClass().getDeclaredFields()) {
				
				if(fd.getAnnotation(Autowired.class) == null) {continue;}
				Autowired autowired = fd.getAnnotation(Autowired.class);
				String beanName = autowired.value().trim();
				if(beanName.isEmpty()) {
					// 获得类名
					beanName = fd.getType().getSimpleName();
				}
				// 允许访问
				fd.setAccessible(true);
				// 设值
				try {
					fd.set(entry.getValue(), beanMap.get(getBeanName(beanName)));
				} catch (IllegalAccessException e) {
					e.printStackTrace();
					continue;
				}
			}
		}
	}

	private void doHandlerMapping() {
		if(beanMap.isEmpty()) {return;}
		Class<?> clazz = null;
		RequestMapping requestMapping = null;
		String hostName = null;
		for(Entry<String, Object> entry : beanMap.entrySet()) {
			// controller才要映射
			if(entry.getValue().getClass().getAnnotation(Controller.class) == null) {continue;}
			clazz = entry.getValue().getClass();
			requestMapping = clazz.getAnnotation(RequestMapping.class);
			hostName = requestMapping == null ? "" : requestMapping.value();
			Method[] methods = clazz.getMethods();
			for(Method md : methods) {
				if(!md.isAnnotationPresent(RequestMapping.class)) {continue;}
				requestMapping = md.getAnnotation(RequestMapping.class);
				handlerMapping.put(hostName + requestMapping.value(), new HandlerMapping(entry.getValue(), md));
			}
		}
 	}

	private String getBeanName(String className) {
		char[] arr = className.toCharArray();
		arr[0] += 32;

		return String.valueOf(arr);
	}

}
